工厂函数创建不同类型的 Ability 实例及权限测试
在 CaslAbilityService 重构完成后,需要通过 buildAbility 工厂方法对三种策略(JSON / MongoDB / Function)逐一进行端到端测试,验证 IPolicy 接口到 AbilityType 实例的完整转换链路。
测试数据结构
// 在 PolicyGuard 中构建测试数据
const policies: IPolicy[] = [
{
type: 0, // JSON 类型
effect: 'can',
action: 'read',
subject: 'Article',
fields: ['title', 'description'],
conditions: { private: false }
}
];
typescript
Type=0:JSON 基础条件测试
配置
const policies: IPolicy[] = [
{
type: 0,
effect: 'can',
action: 'read',
subject: 'Article',
fields: ['title', 'description'],
conditions: { private: false }
}
];
const abilities = this.caslAbilityService.buildAbility(policies);
typescript
验证结果
| Article 实例属性 | fields 输出 | 原因 |
|---|---|---|
{ private: false } | ['title', 'description'] | conditions 匹配 |
{ private: true } | [] | conditions 不匹配 |
import { permittedFieldsOf } from '@casl/ability/extra';
for (const ability of abilities) {
const fields = permittedFieldsOf(ability, 'read', article);
console.log('Permitted fields:', fields);
}
typescript
Type=1:MongoDB 查询测试
配置
const policies: IPolicy[] = [
{
type: 1,
effect: 'can',
action: 'read',
subject: 'Article',
conditions: {
$or: [
{ authorId: 1 },
{ private: true }
]
}
}
];
const abilities = this.caslAbilityService.buildAbility(policies);
typescript
验证结果
| Article 实例 | 权限结果 | 原因 |
|---|---|---|
{ authorId: 11 } | fields 全部返回 | $or 条件均不满足 (authorId!=1, private!=true),NOR 语义下为可读 |
{ authorId: 1 } | [] 空数组 | $or 中 authorId: 1 匹配 |
注意:
$or和$nor语义不同。$or要求至少满足一个条件;$nor要求所有条件都不满足。
Type=2:函数条件测试
配置
const policies: IPolicy[] = [
{
type: 2,
effect: 'can',
action: 'read',
subject: 'Article',
conditions: 'authorId === user.id',
args: ['user']
}
];
// args 通过 buildAbility 第二个参数传入
const user = { id: 1 };
const abilities = this.caslAbilityService.buildAbility(policies, user);
typescript
验证结果
| Article 实例 | user.id | 权限结果 |
|---|---|---|
{ authorId: 1 } | 1 | fields 全部返回 |
{ authorId: 2 } | 1 | [] 空数组 |
常见调试问题
args is not iterable:new Function接收展开参数时,需确保 args 是数组:
// 错误写法
fn(...args); // 如果 args 不是数组会报错
// 正确写法
fn(...[args]); // 将 args 包装为数组
typescript
authorId is not defined:检查 conditions 字符串中的变量名是否与 subject 实例属性名一致。
三种类型的完整测试对比
// PolicyGuard 中的测试入口
async canActivate(context: ExecutionContext): Promise<boolean> {
// 测试三种类型
const testCases: { type: number; policy: IPolicy; args?: any }[] = [
{
type: 0,
policy: {
type: 0, effect: 'can', action: 'read',
subject: 'Article',
fields: ['title', 'description'],
conditions: { private: false }
}
},
{
type: 1,
policy: {
type: 1, effect: 'can', action: 'read',
subject: 'Article',
conditions: { $or: [{ authorId: 1 }, { private: true }] }
}
},
{
type: 2,
policy: {
type: 2, effect: 'can', action: 'read',
subject: 'Article',
conditions: 'authorId === user.id',
args: ['user']
},
args: { id: 1 }
}
];
for (const testCase of testCases) {
const abilities = this.caslAbilityService.buildAbility(
[testCase.policy],
testCase.args
);
console.log(`Type ${testCase.type} test:`, abilities);
}
return true;
}
typescript
测试通过后的下一步
三种策略的工厂函数全部验证通过后,核心工作转向:
- 数据库设计:定义 Policy 表和关联关系表
- PolicyGuard 集成:从数据库读取 Policy 数据,调用
buildAbility构建实例 - 实际权限判断:在 Guard 中用 ability 实例对请求进行授权判断
↑